import { groupBy, throttle } from 'lodash'
import { IS_ELECTRON, useEnv } from '@/store/useEnv'
import { useRoute } from 'vue-router'
import { FileType, GetNasFileListParams, GetNasFileListResponse, NasFile } from '@global/types'
import { computed, nextTick, reactive, toRefs, Ref, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDialog, useMessage, useNotification } from '@/main'
import { useApi } from '@/api'
import useUserInfo from '@/store/useUserInfo'
import streamSaver from 'streamsaver'
import '@/utils/zipStream.js'
import QrCode from 'qrcode'
import { useMusic } from '@/store/useMusic'
import list2Tree from '@/utils/list2Tree'
import { ReadStream, Stats, WriteStream } from 'fs-extra'
import path from 'path'
import Axios from 'axios'
import { getItem, setItem } from '@/utils/storage'

let createWriteStream: any,
  mkdirp: (dir: string) => Promise<void>,
  remove: (dir: string, callback?: (err: Error) => void) => Promise<void>,
  stat: (path: string, callback?: (err: NodeJS.ErrnoException, stats: Stats) => any) => Promise<Stats>
if (IS_ELECTRON) {
  const fs = require('fs-extra')
  createWriteStream = fs.createWriteStream
  mkdirp = fs.mkdirp
  remove = fs.remove
  stat = fs.stat
}

const axios = Axios.create()
axios.defaults.adapter = require('axios/lib/adapters/http')

type Download = Omit<NasFile, 'uploaded' | 'uploadFinish' | 'thumbnailExt' | 'hasChild'> & {
  dId: string
  realFilePath: string
  loaded: number
  stream?: WriteStream
  errMsg: string
}

type Props = {
  params: Ref<GetNasFileListParams>
  fileData: Ref<GetNasFileListResponse>
  getFileList: () => Promise<void>
  selectFileList: Ref<NasFile[]>
  uploadFileInputRef: Ref<HTMLInputElement>
}
export default ({ params, fileData, getFileList, selectFileList, uploadFileInputRef }: Props) => {
  const env = useEnv()
  const { t } = useI18n()
  const route = useRoute()
  const api = useApi()
  const music = useMusic()
  const { saveDownloadFilePath } = useUserInfo()

  // if (env.NODE_ENV === 'production') streamSaver.mitm = env.origin + '/static/html/mitm.html'
  const state = reactive({
    /** 右键菜单属性 */
    contextMenu: {
      show: false,
      x: 0,
      y: 0
    },
    /** 创建文件夹 */
    createDirModel: {
      show: false,
      dirName: ''
    },
    /** 复制文件列表 */
    copyList: [] as NasFile[],
    /** 移动文件列表 */
    moveList: [] as NasFile[],
    /** 重命名 */
    renameFileModel: {
      show: false,
      fileInfo: {} as NasFile,
      oldFileName: '',
      oldExt: null as null | string
    },
    /** 下载文件链接的二维码 */
    downloadQrCodeModel: {
      show: false,
      fileName: '',
      dataBase64: ''
    },
    /** 详细文件信息 */
    detailModel: {
      show: false,
      fileInfo: {} as NasFile
    },

    /** 是否覆盖文件 */
    isRepeatAll: false,
    /** 是否删除原始文件 */
    isDelRawFile: false,
    /** 正在下载的文件列表 */
    downloadingList: [] as Download[],
    /** 暂停下载的文件列表 */
    pauseList: [] as Download[],
    /** 等待下载的文件列表 */
    pendingList: [] as Download[],
    /** 下载完成的列表 */
    finishList: [] as Download[],
    /** 下载异常的列表 */
    errorList: [] as Download[],
    /** 下载文件模态框显示 */
    downloadModelShow: false
  })

  const downloadList = computed(() => {
    return [
      ...state.downloadingList.map(item => ({ ...item, status: 'downloading' as const })),
      ...state.pendingList.map(item => ({ ...item, status: 'pending' as const })),
      ...state.pauseList.map(item => ({ ...item, status: 'pause' as const })),
      ...state.finishList.map(item => ({ ...item, status: 'finish' as const })),
      ...state.errorList.map(item => ({ ...item, status: 'error' as const }))
    ]
  })

  /** 右键菜单列表 */
  const contextMenuOpts = computed(() => {
    if (state.copyList.length) {
      return [
        { label: t('paste'), key: 'paste' },
        { label: t('cancel-copy'), key: 'cancel-copy' }
      ]
    }

    if (state.moveList.length) {
      return [
        { label: t('paste'), key: 'shear' },
        { label: t('cancel-move'), key: 'cancel-move' }
      ]
    }

    const selectFileNum = selectFileList.value.length
    if (!selectFileNum) {
      return [
        { label: t('refresh'), key: 'refresh' },
        { label: t('upload-file'), key: 'upload-file' },
        { label: t('create-dir'), key: 'create-dir' }
      ]
    }

    const opts = [
      { label: t('copy'), key: 'copy' },
      { label: t('move'), key: 'move' },
      { label: t('delete'), key: 'delete' },
      { label: t('download-file'), key: 'download-file' }
    ]
    if (selectFileNum === 1) opts.push({ label: t('rename'), key: 'rename' })
    if (selectFileNum === 1) {
      const [file] = selectFileList.value
      opts.push({ label: t('detail-info'), key: 'detail-info' })
      if (file.fileType !== FileType.dir) opts.push({ label: t('file-url-qrcode'), key: 'file-qrcode' })
    }
    return opts
  })

  /** 显示右键菜单列表 */
  const showContextMenu = (e: MouseEvent) => {
    state.contextMenu.show = false
    nextTick().then(() => {
      const { clientX, clientY } = e
      state.contextMenu.x = clientX
      state.contextMenu.y = clientY
      state.contextMenu.show = true
    })
  }

  /** 获取下载项目的 icon 图标类名 */
  const getDownloadIconClassName = (file: Download) => {
    const isDownloading = state.downloadingList.find(({ id }) => id === file.id)
    if (isDownloading) return 'fa-pause text-blue-400 cursor-pointer'
    const isStop = [...state.pendingList, ...state.pauseList].find(({ id }) => id === file.id)
    if (isStop) return 'fa-play text-blue-400 cursor-pointer'
    const isError = state.errorList.find(({ id }) => id === file.id)
    if (isError) return 'fa-close text-red-400 cursor-pointer pointer-events-none'
    const isFinish = state.finishList.find(({ id }) => id === file.id)
    if (isFinish) return 'fa-check text-green-400 pointer-events-none'
  }

  /** 获取下载进度 */
  const getDownloadPercentage = (file: Download) => {
    return Math.floor(((file.loaded || 0) / (file.fileSize || 0)) * 100)
  }

  /** 删除单个下载任务 */
  const delDownloadTask = (file: typeof downloadList.value[0]) => {
    if (state.isDelRawFile) {
      const downloadItem = downloadList.value.find(item => item.dId === file.dId)
      remove(downloadItem!.realFilePath).catch(err =>
        useDialog().error({
          title: `${t('delete-file')}【${downloadItem!.realFilePath}】${t('fail')}，${t('plase-delete-file')}`,
          content: JSON.stringify(err).replace(/"|{|}/g, '')
        })
      )
    }
    switch (file.status) {
      case 'downloading':
        state.downloadingList = state.downloadingList.filter(({ dId }) => dId !== file.dId)
        break
      case 'pause':
        state.pauseList = state.pauseList.filter(({ dId }) => dId !== file.dId)
        break
      case 'pending':
        state.pendingList = state.pendingList.filter(({ dId }) => dId !== file.dId)
        break
      case 'finish':
        state.finishList = state.finishList.filter(({ dId }) => dId !== file.dId)
        break
      case 'error':
        state.errorList = state.errorList.filter(({ dId }) => dId !== file.dId)
        break
    }
  }

  /** 删除所有任务 */
  const delAllDwonloadTasks = async () =>
    useDialog().warning({
      title: t('confirm-delete-all-download-tasks'),
      content: state.isDelRawFile ? t('is-select-delete-raw-files') : undefined,
      positiveText: t('confirm'),
      negativeText: t('cancel'),
      onPositiveClick: () => downloadList.value.forEach(item => delDownloadTask(item))
    })

  /** 获取文件地址 */
  const getFileUrl = (filePath: string) =>
    `${env.staticUrl}/users/${useUserInfo().id}/nas/file/${encodeURIComponent(filePath)}`

  /** 下载文件 */
  const downloadOneFile = async (file: Download) => {
    const { data: readStream }: { data: ReadStream } = await axios.get(getFileUrl(file.filePath), {
      responseType: 'stream',
      headers: {
        Range: `bytes=${file.loaded || 0}-`
      }
    })
    const writeStream: WriteStream = createWriteStream(file.realFilePath, { flags: 'a' })
    const stream = readStream.pipe(writeStream)

    // 更新下载进度
    const updateSize = async () => {
      const { size } = await stat(file.realFilePath)

      const isStop = [...state.pauseList, ...state.pendingList].find(item => item.dId === file.dId)
      if (isStop) {
        isStop.loaded = size
        return
      }

      state.downloadingList = state.downloadingList.filter(item => {
        if (item.dId === file.dId) {
          item.loaded = size
          if (item.loaded === item.fileSize) {
            item.stream = undefined
            state.finishList.unshift(item)
            useMessage().success(`${t('success-download-file')}${item.fileName}`)
            return false
          }
        }
        return true
      })
    }
    // 开始下载
    stream.on('open', () => {
      state.pendingList = state.pendingList.filter(item => {
        if (item.dId === file.dId) {
          item.stream = stream
          state.downloadingList.unshift(item)
          return false
        }
        return true
      })
    })
    // 节流更新下载进度
    stream.on('drain', throttle(updateSize, 100))
    // 下载异常
    stream.on('error', err => {
      const errMsg = JSON.stringify(err).replace(/[{}"]g/, '')
      useNotification().error({
        title: '下载错误',
        content: errMsg
      })
      state.downloadingList = state.downloadingList.filter(item => {
        if (item.dId === file.dId) {
          item.errMsg = errMsg
          state.errorList.push(item)
          return false
        }
      })
    })
    // 中止下载
    stream.on('close', updateSize)
  }

  /** 更改下载任务状态 */
  const changeDownloadStatus = (file: Download) => {
    let returnFlag = false

    // 暂停任务
    state.downloadingList = state.downloadingList.filter(item => {
      if (item.dId === file.dId) {
        item.stream!.close()
        item.stream = undefined
        state.pauseList.push(item)
        returnFlag = true
        return false
      }
      return true
    })
    if (returnFlag) return

    // 继续下载
    state.pauseList = state.pauseList.filter(item => {
      if (item.dId === file.dId) {
        state.pendingList.unshift(item)
        downloadOneFile(item)
        returnFlag = true
        return false
      }
      return true
    })
    if (returnFlag) return

    // 提前下载
    state.pendingList.some(item => {
      if (item.dId === file.dId) {
        downloadOneFile(file)
        returnFlag = true
        return true
      }
    })
    if (returnFlag) return

    state.errorList = state.errorList.filter(item => {
      if (item.dId === file.dId) {
        state.pendingList.unshift(item)
        downloadOneFile(item)
        return false
      }
      return true
    })
  }

  let downloadFile = async () => {}
  if (!IS_ELECTRON) {
    // web 端下载
    downloadFile = async () => {
      let urlList = [] as string[][]
      const downloadFileList = [...selectFileList.value]
      for (const i in downloadFileList) {
        const item = selectFileList.value[i]
        // 如果是文件
        if (item.fileType !== FileType.dir) {
          urlList.push([item.fileName, getFileUrl(item.filePath)])
          // 如果是文件夹
        } else {
          const inDirFileList: NasFile[] = await api.get('/nas/list_deep', {
            params: { parentPath: item.filePath }
          })
          const inDirUrlList = inDirFileList
            .filter(item => item.fileType !== FileType.dir)
            .map(item => {
              return [item.fileName, getFileUrl(item.filePath)]
            })
          urlList = [...urlList, ...inDirUrlList]
          // 重命名文件
          urlList.forEach(item => {
            const repeatFileNameList = urlList.filter(([fileName]) => item[0] === fileName)
            repeatFileNameList.forEach((item, i) => {
              if (!i) return
              let newName = `${t('copy-file')}-${i}-${item[0]}`
              let isStillRepeatName = true
              while (isStillRepeatName) {
                isStillRepeatName = urlList.some(([name]) => name === newName)
                newName = `${t('copy-file')}-${++i}-${item[0]}`
              }
              item[0] = newName
            })
          })
        }
      }

      const urlListIterator = urlList.values()
      const fileStream = streamSaver.createWriteStream(Date.now() + '.zip')
      const readableZipStream = new (window as any).ZIP({
        pull(ctrl) {
          const it = urlListIterator.next()
          if (it.done) ctrl.close()
          else {
            const [name, url] = it.value
            useMessage().success(`${t('import-file')}：${name}`)
            return fetch(url).then(res => ctrl.enqueue({ name, stream: () => res.body }))
          }
        }
      })
      if (window.WritableStream && readableZipStream.pipeTo) {
        return readableZipStream.pipeTo(fileStream)
        // .then(() => {
        //   console.log('下载成功')
        // })
        // .catch(err => console.log(err))
      }
    }

    // PC 端下载
  } else {
    onMounted(() => {
      const downloadListStorage = getItem('downloadList') as typeof downloadList.value
      if (downloadListStorage) {
        const { downloading, pending, pause, finish, error } = groupBy(downloadListStorage, 'status')
        ;[state.downloadingList, state.pendingList, state.pauseList, state.finishList, state.errorList] = [
          [],
          [...(downloading || []), ...(pending || [])],
          pause || [],
          finish || [],
          error || []
        ]
      }
    })
    watch(downloadList, list => setItem('downloadList', list))

    downloadFile = async () => {
      let allList = selectFileList.value.map(
        item =>
          ({
            id: item.id,
            uid: item.uid,
            loaded: 0,
            fileName: item.fileName,
            filePath: item.filePath,
            fileSize: item.fileSize,
            fileType: item.fileType,
            createTime: item.createTime
          } as Download)
      )
      const dirList = allList.filter(item => item.fileType === FileType.dir)
      for (const dir of dirList) {
        const inDirFileList: Download[] = await api.get('/nas/list_deep', {
          params: { parentPath: dir.filePath }
        })
        allList = allList.concat(inDirFileList)
      }

      allList = allList
        .map(item => {
          const realFilePath = path.join(
            saveDownloadFilePath,
            item.filePath.replace(params.value.parentPath || '', '').replaceAll(/@@@/g, '/')
          )
          return {
            ...item,
            realFilePath,
            dId: `${item.id}-${item.uid}-${realFilePath}`
          }
        })
        .filter(item => {
          const existItem = downloadList.value.find(existItem => existItem.dId === item.dId)
          if (existItem) {
            switch (existItem.status) {
              case 'error':
                state.errorList = state.errorList.filter(errItem => {
                  if (errItem.dId === item.dId) {
                    state.pendingList.push(item)
                    return false
                  }
                  return true
                })
                break
              case 'pause':
                state.pauseList = state.pauseList.filter(pauseItem => {
                  if (pauseItem.dId === item.dId) {
                    state.pendingList.push(item)
                    return false
                  }
                  return true
                })
                break
              case 'finish':
                useMessage().warning(`已存在的文件：${item.realFilePath}`)
                break
            }
            return false
          }
          return true
        })

      state.pendingList = allList.filter(item => item.fileType !== FileType.dir)
      if (state.pendingList.length) state.downloadModelShow = true

      const fileTree = list2Tree(allList, {
        idKey: 'filePath',
        pidKey: 'parentPath',
        childrenKey: 'children',
        pid: params.value.parentPath
      })

      type FileTree = typeof fileTree

      const downloadFileList = async (fileTree: FileTree) => {
        for (const file of fileTree) {
          const realFileInfo = await stat(file.realFilePath).catch(() => {})
          // 如果是文件夹
          if (file.fileType === FileType.dir) {
            // 如果路径不存在文件夹
            if (!realFileInfo) {
              // 创建文件夹
              await mkdirp(file.realFilePath)
              useMessage().success(t('success-create-dir') + `：${file.realFilePath}`)
              // 如果路径存在该文件且不是文件夹 && 未选中覆盖所有文件
            } else if (!realFileInfo.isDirectory && !state.isRepeatAll) {
              try {
                state.isRepeatAll = await new Promise((resolve, reject) => {
                  useDialog().warning({
                    title: t('find-repeat-file'),
                    content: t('yes-or-no') + file.realFilePath + t('replace-folder'),
                    negativeText: t('confirm-replace'),
                    positiveText: t('replace-all'),
                    onClose: reject,
                    onNegativeClick: () => resolve(false),
                    onPositiveClick: () => resolve(true)
                  })
                })
              } catch (err) {
                continue
              }
              remove(file.realFilePath)
              await mkdirp(file.realFilePath)
              useMessage().success(`${t('success-replace-floder')}：${file.realFilePath}`)
            }
            downloadFileList(file.children) // 递归
          }
          // 如果是文件
          if (file.fileType !== FileType.dir) {
            // 如果文件不存在
            if (!realFileInfo) downloadOneFile(file)
            else if (state.isRepeatAll) {
              await remove(file.realFilePath)
              downloadOneFile(file)
            } else if (!state.isRepeatAll) {
              try {
                state.isRepeatAll = await new Promise((resolve, reject) => {
                  useDialog().warning({
                    title: t('find-repeat-file'),
                    content: `${t('confirm-repeat-file')}：【${file.realFilePath}】？`,
                    negativeText: t('confirm-replace'),
                    positiveText: t('replace-all'),
                    onClose: reject,
                    onNegativeClick: () => resolve(false),
                    onPositiveClick: () => resolve(true)
                  })
                })
              } catch (err) {
                continue
              }
              await remove(file.realFilePath)
              downloadOneFile(file)
            }
          }
        }
      }

      downloadFileList(fileTree)
    }
  }

  /** 点击菜单选项 */
  const clickMenuItem = async (key: string) => {
    state.contextMenu.show = false
    switch (key) {
      // —————————————————————————————————刷新文件列表—————————————————————————————————————
      case 'refresh':
        await getFileList()
        useMessage().success(t('refreshed-file-list'))
        break
      // —————————————————————————————————上传文件—————————————————————————————————————
      case 'upload-file':
        uploadFileInputRef.value.click()
        break
      // —————————————————————————————————创建文件夹—————————————————————————————————————
      case 'create-dir':
        state.createDirModel.show = true
        break
      // —————————————————————————————————移动文件—————————————————————————————————————
      case 'move':
        state.moveList = selectFileList.value
        break
      case 'cancel-move':
        state.moveList = []
        break
      case 'shear': {
        const [file] = state.moveList
        const sourceParentPath = file.parentPath
        const targetParentPath = route.query.parentPath || ''
        if (sourceParentPath === targetParentPath) return useMessage().warning(t('parentPath-no-change'))
        fileData.value = await api.post(
          '/nas/copy_files',
          {
            filePathList: state.moveList.map(item => item.filePath),
            parentPath: route.query.parentPath || ''
          },
          { params: params.value }
        )
        fileData.value = await api.post(
          '/nas/del_files',
          { filePathList: state.moveList.map(item => item.filePath) },
          { params: params.value }
        )
        state.moveList = []
        break
      }
      // —————————————————————————————————复制文件—————————————————————————————————————
      case 'copy':
        state.copyList = selectFileList.value
        break
      case 'cancel-copy':
        state.copyList = []
        break
      case 'paste': {
        fileData.value = await api.post(
          '/nas/copy_files',
          {
            filePathList: state.copyList.map(item => item.filePath),
            parentPath: route.query.parentPath || ''
          },
          { params: params.value }
        )

        // 如果包含音乐文件，更新音乐文件列表
        const hasMusic = state.copyList.find(item => item.fileType === FileType.audio)
        if (hasMusic) music.updateMusicList()

        state.copyList = []
        break
      }
      // —————————————————————————————————下载文件—————————————————————————————————————
      case 'download-file': {
        await downloadFile()
        break
      }
      // —————————————————————————————————重命名—————————————————————————————————————
      case 'rename': {
        const file = selectFileList.value[0]
        const extMatch = file.fileName.match(/\..+$/)
        state.renameFileModel.oldFileName = file.fileName
        state.renameFileModel.show = true
        state.renameFileModel.fileInfo = file
        state.renameFileModel.oldExt = extMatch ? extMatch[0] : null
        break
      }
      // —————————————————————————————————删除文件—————————————————————————————————————
      case 'delete': {
        const filePathList = selectFileList.value.map(item => item.filePath)
        fileData.value = await api.post('/nas/del_files', { filePathList }, { params: params.value })
        // 如果包含音乐文件，更新音乐文件列表
        const hasMusic = selectFileList.value.find(item => item.fileType === FileType.audio)
        if (hasMusic) music.updateMusicList()
        break
      }
      // —————————————————————————————————文件二维码—————————————————————————————————————
      case 'file-qrcode': {
        const [file] = selectFileList.value
        const downloadUrl = `${env.staticUrl}/users/${useUserInfo().id}/nas/file/${file.filePath}`
        Object.assign(state.downloadQrCodeModel, {
          show: true,
          fileName: file.fileName,
          dataBase64: await QrCode.toDataURL(downloadUrl, { width: 300 })
        })
        break
      }
      // —————————————————————————————————详细信息—————————————————————————————————————
      case 'detail-info': {
        state.detailModel.fileInfo = await api.get('/nas/file', {
          params: { id: selectFileList.value[0].id }
        })
        state.detailModel.show = true
        break
      }
    }
    selectFileList.value = []
  }

  /** 创建文件夹 */
  const createDir = async () => {
    const dirName = state.createDirModel.dirName
    if (!dirName) return useMessage().warning(t('please-input-dir-name'))
    const parentPath = (route.query.parentPath as string) || ''
    const dirPath = parentPath + dirName + '@@@'
    fileData.value = await api.post('/nas/create_dir', { dirPath }, { params: params.value })
    state.createDirModel.dirName = ''
    state.createDirModel.show = false
  }

  /** 修改文件名 */
  const renameFile = async () => {
    const { id } = state.renameFileModel.fileInfo
    const { oldFileName: fileName } = state.renameFileModel
    state.renameFileModel.show = false
    fileData.value = await api.post('/nas/rename', { id, fileName }, { params: params.value })
  }
  const checkNewFileName = async () => {
    const { oldFileName, oldExt } = state.renameFileModel
    if (!oldFileName) return useMessage().warning(t('please-input-file-name'))
    if (state.renameFileModel.fileInfo.fileType === FileType.dir) return renameFile()
    const extMatch = oldFileName.match(/\..+$/)
    const newExt = extMatch ? extMatch[0] : null
    if (oldExt !== newExt) {
      useDialog().warning({
        title: t('sure-change'),
        content: t('change-ext-tips'),
        negativeText: t('confirm'),
        onNegativeClick: renameFile
      })
    } else renameFile()
  }

  return {
    ...toRefs(state),

    /** 右键菜单配置 */
    contextMenuOpts,
    /** 显示右键菜单列表 */
    showContextMenu,
    /** 点击菜单选项 */
    clickMenuItem,

    /** 创建文件夹 */
    createDir,

    /** 检查文件扩展名 */
    checkNewFileName,

    /** 下载列表 */
    downloadList,
    /** 获取下载项目的icon类名 */
    getDownloadIconClassName,
    /** 获取下载进度 */
    getDownloadPercentage,
    /** 改变任务状态 */
    changeDownloadStatus,
    /** 删除下载任务 */
    delDownloadTask,
    /** 删除所有下载任务 */
    delAllDwonloadTasks
  }
}
